<?php declare(strict_types=1);
/**
 * @author      xianganyall <xianganyall@gmail.com>
 * @copyright   2023-2025 owner
 **/

namespace Srv\Libs\Frame;

use Exception;
use Google\Protobuf\Any;
use Google\Protobuf\Internal\MapField;
use Google\Protobuf\ListValue;
use Google\Protobuf\Struct;
use Google\Protobuf\Value;
use JsonSerializable;
use ReflectionClass;
use ReflectionProperty;
use ReflectionException;
use Google\Protobuf\Internal\Message;
use Google\Protobuf\Internal\RepeatedField;
use Srv\Libs\Common\CommJson;
use Srv\Libs\Common\CommString;
use Srv\Libs\Common\CommValid;

abstract class OAbstract extends Message implements JsonSerializable
{
    /**
     * @return array
     * __debugInfo
     */
    final public function __debugInfo():array
    {
        return [];
    }

    /**
     * @param string $str
     * @return bool
     * mergeFromStrTry
     */
    final public function mergeFromStrTry(string $str):bool
    {
        if(strlen($str) < 1) return true;
        try{
            $this->mergeFromString($str);
        }catch(Exception $Exception){
            unset($Exception);
            return false;
        }
        return true;
    }

    /**
     * @param array $aryData
     * @param bool $ignoreUnknown
     * @param bool $strict
     * @param string $errKeyName
     * @param string $errKeyType
     * @param $errKeyValue
     * @return bool
     * mergeFromArrayTry
     */
    final public function mergeFromArrayTry(array $aryData, bool $ignoreUnknown = false, bool $strict = false, string &$errKeyName = '', string &$errKeyType = '', &$errKeyValue = null):bool
    {
        if(count($aryData) < 1) return true;
        $jsonStr            = CommJson::encodeArray($aryData);
        try{
            $defValData     = $this->getOMessageJsonArrayForDefault(null, []);
            $this->mergeFromArrayCheck('', $defValData, $aryData, $strict, $errKeyName, $errKeyType, $errKeyValue);   // proto前置检查
            $this->mergeFromJsonString($jsonStr, $ignoreUnknown);
        }catch(Exception $Exception){
            Logs::error($Exception->getMessage()."\n\n".$Exception->getTraceAsString()."\n\n".$jsonStr, __LINE__, __FILE__);
            return false;
        }
        return true;
    }

    /**
     * @param string $jsonStr
     * @param bool $ignoreUnknown
     * @param bool $strict
     * @param string $errKeyName
     * @param string $errKeyType
     * @param $errKeyValue
     * @return bool
     * mergeFromJsonStrTry
     */
    final public function mergeFromJsonStrTry(string $jsonStr, bool $ignoreUnknown = false, bool $strict = false, string &$errKeyName = '', string &$errKeyType = '', &$errKeyValue = null):bool
    {
        if(strlen($jsonStr) < 1) return true;
        try{
            $jsonData       = CommJson::decodeArray($jsonStr);
            $defValData     = $this->getOMessageJsonArrayForDefault(null, []);
            $this->mergeFromArrayCheck('', $defValData, $jsonData, $strict, $errKeyName, $errKeyType, $errKeyValue);  // proto前置检查
            $this->mergeFromJsonString($jsonStr, $ignoreUnknown);
        }catch(Exception $Exception){
            Logs::error($Exception->getMessage()."\n\n".$Exception->getTraceAsString()."\n\n".$jsonStr, __LINE__, __FILE__);
            return false;
        }
        return true;
    }

    /**
     * @param string $protoKeyPrefix
     * @param array $defValData
     * @param array $jsonData
     * @param bool $strict
     * @param string $errKeyName
     * @param string $errKeyType
     * @param $errKeyValue
     * @return void
     * @throws Exception
     * mergeFromArrayCheck
     */
    private function mergeFromArrayCheck(string $protoKeyPrefix, array $defValData, array $jsonData, bool $strict = false, string &$errKeyName = '', string &$errKeyType = '', &$errKeyValue = null):void
    {
        if(count($defValData) < 1 || count($jsonData) < 1) return;
        foreach ($defValData as $protoKey => $protoVal){
            if(!is_string($protoKey) || !isset($jsonData[$protoKey])) continue;
            $jsonVal                = $jsonData[$protoKey];
            $protoType              = gettype($protoVal);
            if($protoType === 'NULL') continue;     // 不检查的类型
            $dataValType            = gettype($jsonVal);
            $protoTypeSub           = '';
            if($protoType === 'array'){
                $isArrayList        = CommValid::isArrayList($protoVal);
                $protoTypeSub       = $isArrayList ? 'arrayList' : 'arrayObject';
                if($protoType === $dataValType){
                    if($isArrayList){
                        if(CommValid::isArrayList($jsonVal)) continue;
                    }else{
                        if(CommValid::isArrayObject($jsonVal)){
                            $this->mergeFromArrayCheck($protoKeyPrefix.$protoKey.'.', $protoVal, $jsonVal, $strict, $errKeyName, $errKeyType, $errKeyValue);
                            continue;
                        }
                    }
                }
            }else{
                if($protoType === $dataValType) continue;
                if($strict){    // 兼容类型
                    $dataIsStr  = $dataValType === 'string';
                    if($protoType === 'integer' && $dataIsStr && (strlen($jsonVal) < 1 || is_numeric($jsonVal))) continue;
                    if($protoType === 'double' && $dataIsStr && (strlen($jsonVal) < 1 || preg_match('/^\d+\.\d+$/', $jsonVal))) continue;
                    if($protoType === 'boolean' && $dataIsStr && in_array(strtolower($jsonVal), ['false', 'true', '0', '1'], true)) continue;
                }
            }
            $errKeyName     = $protoKeyPrefix.$protoKey;
            $errKeyType     = strlen($protoTypeSub) > 0 ? $protoTypeSub : $protoType;
            $errKeyValue    = $jsonVal;
            throw new Exception('mergeFromJsonStrTry: check proto field type error, '.$errKeyName.', must type is: '.(strlen($protoTypeSub) > 0 ? $protoTypeSub : $protoType));
        }
    }

    /**
     * @param RepeatedField $RepeatedField
     * @return array
     * toArrayList
     */
    final public function toArrayList(RepeatedField $RepeatedField):array
    {
        $repeatedList           = [];
        if($RepeatedField->count() < 1) return $repeatedList;
        foreach($RepeatedField->getIterator() as $RepeatedFieldItem){
            $repeatedList[]     = $RepeatedFieldItem;
        }
        return $repeatedList;
    }

    /**
     * @param VOAbstract|null $VOAbstract
     * @return Any|null
     * toAnyMessage
     */
    final public function toAnyMessage(?VOAbstract $VOAbstract = null):?Any
    {
        $AnyMessage     = new Any();
        try{
            $AnyMessage->pack(is_null($VOAbstract) ? $this : $VOAbstract);
        }catch(Exception $Exception){
            unset($Exception);
            return null;
        }
        return $AnyMessage;
    }

    /**
     * @param Any $AnyMessage
     * @param string $AnyMessageClassName
     * @return Message|null
     * anyTypeUrlToMessage
     */
    final public function anyTypeUrlToMessage(Any $AnyMessage, string $AnyMessageClassName = ''):?Message
    {
        if(strlen($AnyMessageClassName) > 0){
            $typeUrlPrefix          = 'type.googleapis.com/';   // 类型Url[固定](取决于编译PHP扩展的配置)
            $typeUrlSet             = $typeUrlPrefix.implode('.', explode('\\', $AnyMessageClassName));
            $AnyMessage->setTypeUrl($typeUrlSet);
        }else{
            $anyTypeUrl             = $AnyMessage->getTypeUrl();
            if(strlen($anyTypeUrl) < 3) return null;
            $pos                    = strpos($anyTypeUrl, '/');
            if($pos < 1) return null;
            $AnyMessageClassName    = preg_replace('/\./', '\\', substr($anyTypeUrl, $pos + 1));
        }
        if(strlen($AnyMessageClassName) < 1 || !CommValid::isExistsClass($AnyMessageClassName)) return null;
        try{
            $classUnpack    = $AnyMessage->unpack();
            if(!($classUnpack instanceof Message)) return null;
            $Message                = new $AnyMessageClassName();
            if(!($Message instanceof Message)) return null;
            $Message->mergeFrom($classUnpack);
        }catch(Exception $Exception){
            unset($Exception);
            return null;
        }
        return $Message;
    }

    /**
     * @return array
     * jsonSerialize
     */
    final public function jsonSerialize():array
    {
        return $this->getOMessageJsonArray($this, [], false);
    }

    /**
     * @param array $ignoreList
     * @param bool $isAll
     * @return array
     * getDataJsonArray
     */
    final public function getDataJsonArray(array $ignoreList, bool $isAll = false):array
    {
        return $this->getOMessageJsonArray($this, $ignoreList, $isAll);
    }

    /**
     * @param array $ignoreList
     * @param bool $isAll
     * @param string $emptyJson
     * @return string
     * getDataJsonStr
     */
    final public function getDataJsonStr(array $ignoreList, bool $isAll = false, string $emptyJson = CommJson::JSON_DEFAULT_EMPTY_STRING):string
    {
        return CommJson::encodeArray($this->getOMessageJsonArray($this, $ignoreList, $isAll), $emptyJson);
    }

    /**
     * @param OAbstract $OMessage
     * @param array $ignoreList
     * @param bool $isAll
     * @return array
     * getOMessageJsonArray
     */
    final public function getOMessageJsonArray(OAbstract $OMessage, array $ignoreList, bool $isAll):array
    {
        $jsonData                   = [];
        $ReflectionClass            = new ReflectionClass($OMessage);
        $ReflectionPropertyList     = $ReflectionClass->getProperties(ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PRIVATE);
        if(count($ReflectionPropertyList) < 1) return $jsonData;
        $methodList                 = array_column($ReflectionClass->getMethods(ReflectionProperty::IS_PUBLIC), 'name');
        if(count($methodList) < 1) return $jsonData;
        foreach($ReflectionPropertyList as $ReflectionProperty){
            $propertyName           = $ReflectionProperty->getName();
            $propertyDocComment     = $ReflectionProperty->getDocComment();
            if(in_array($propertyName, $ignoreList, true) || CommString::startsWith($propertyName, '__')) continue;
            $methodNameGet          = 'get'.ucfirst(preg_replace_callback('/[^a-z\d]+(\S)/i', function($match){ return strtoupper($match[1]);}, $propertyName));
            if(!in_array($methodNameGet, $methodList, true)) continue;
            $propertyValue          = @call_user_func_array([$OMessage, $methodNameGet], []);
            $isMust                 = !is_string($propertyDocComment) || strpos($propertyDocComment, '[optional]') === false;
            if($propertyValue instanceof OAbstract){
                $jsonData[$propertyName]                    = $this->getOMessageJsonArray($propertyValue, [], $isAll);
            }else if($propertyValue instanceof Any){
                $AnyOAbstract                               = $this->anyTypeUrlToMessage($propertyValue);
                $jsonData[$propertyName]                    = ($AnyOAbstract instanceof OAbstract) ? $this->getOMessageJsonArray($AnyOAbstract, [], $isAll) : null;
            }else if($propertyValue instanceof RepeatedField){
                if($isAll || $isMust || $propertyValue->count() > 0){
                    $jsonData[$propertyName]                = [];
                    foreach($propertyValue->getIterator() as $RepeatedFieldValue){
                        if($RepeatedFieldValue instanceof OAbstract){
                            $jsonData[$propertyName][]      = $this->getOMessageJsonArray($RepeatedFieldValue, [], $isAll);
                        }else{
                            if($isAll || $isMust || ($propertyValue !== null && $propertyValue !== 0 && $propertyValue !== '' && $propertyValue !== false)){
                                $jsonData[$propertyName][]  = $RepeatedFieldValue;
                            }
                        }
                    }
                }
            }else if($propertyValue instanceof MapField){
                if($isAll || $isMust || $propertyValue->count() > 0){
                    $jsonData[$propertyName]                            = [];
                    foreach($propertyValue->getIterator() as $MapFieldKey => $MapFieldValue){
                        if($MapFieldValue instanceof OAbstract){
                            $jsonData[$propertyName][$MapFieldKey]      = $this->getOMessageJsonArray($MapFieldValue, [], $isAll);
                        }else if($MapFieldValue instanceof ListValue || $MapFieldValue instanceof Struct || $MapFieldValue instanceof Value){
                            $jsonData[$propertyName][$MapFieldKey]      = CommJson::decodeArray($MapFieldValue->serializeToJsonString());
                        }else{
                            if($isAll || $isMust || ($propertyValue !== null && $propertyValue !== 0 && $propertyValue !== '' && $propertyValue !== false)){
                                $jsonData[$propertyName][$MapFieldKey]  = $MapFieldValue;
                            }
                        }
                    }
                }
            }else{
                if($isAll || $isMust || ($propertyValue !== null && $propertyValue !== 0 && $propertyValue !== '' && $propertyValue !== false)){
                    $jsonData[$propertyName]                = $propertyValue;
                }
            }
        }
        return $jsonData;
    }

    /**
     * @param OAbstract|null $OMessage
     * @param array $classNameList
     * @return array
     * getOMessageJsonArrayForDefault
     */
    final public function getOMessageJsonArrayForDefault(?OAbstract $OMessage = null, array $classNameList = []):array
    {
        $jsonData                   = [];
        try{
            if(is_null($OMessage)) $OMessage = $this;
            $ReflectionClass        = new ReflectionClass($OMessage);
            $ReflectionPropertyList = $ReflectionClass->getProperties(ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PRIVATE);
            if(count($ReflectionPropertyList) < 1) return $jsonData;
            $methodList             = array_column($ReflectionClass->getMethods(ReflectionProperty::IS_PUBLIC), 'name');
            if(count($methodList) < 1) return $jsonData;
        }catch (ReflectionException $ReflectionException){
            unset($ReflectionException);
            return $jsonData;
        }
        foreach($ReflectionPropertyList as $ReflectionProperty) {
            $propertyName           = $ReflectionProperty->getName();
            if (CommString::startsWith($propertyName, '__')) continue;
            $defVal                 = null;
            if (!$ReflectionProperty->hasType()) {    // 没有申明类型
                $propertyDocComment = $ReflectionProperty->getDocComment();
                if(!is_string($propertyDocComment) || !preg_match('#\*(.*)\s+.*\s+.*<code>(\S+\s+)?(\S+)\s+\S+\s+=\s+(\d+);</code>#', $propertyDocComment, $match)){
                    $jsonData[$propertyName] = $defVal;
                    continue;
                }
                $flag               = strtolower(trim($match[2]));
                $type               = str_replace('.', '\\', trim($match[3], '.'));
                $repeated           = $flag === 'repeated';
                $defValItem         = null;
                switch($type){
                    case 'double':
                    case 'float':{
                        $defValItem = 0.0;
                        break;
                    }
                    case 'int32':
                    case 'uint32':
                    case 'sint32':
                    case 'fixed32':
                    case 'sfixed32':
                    case 'int64':
                    case 'uint64':
                    case 'sint64':
                    case 'fixed64':
                    case 'sfixed64':{
                        $defValItem = 0;
                        break;
                    }
                    case 'bool':{
                        $defValItem = false;
                        break;
                    }
                    case 'string':
                    case 'bytes':{
                        $defValItem = '';
                        break;
                    }
                    default:{
                        if(!in_array($type, $classNameList, true) && CommValid::isExistsClass($type)){
                            $TypeOMessage       = new $type();
                            if($TypeOMessage instanceof OAbstract){
                                $defValItem     = $TypeOMessage->getOMessageJsonArrayForDefault(null, array_merge_recursive($classNameList, [$type]));
                            }else if($TypeOMessage instanceof EnumAbstract){    // 枚举默认值是0
                                $defValItem     = 0;
                            }
                        }
                    }
                }
                $defVal = $repeated ? [$defValItem] : $defValItem;
            }else{
                $propertyTypeName   = $ReflectionProperty->getType()->getName();
                switch($propertyTypeName){
                    case 'bool':{
                        $defVal = false;
                        break;
                    }
                    case 'int':{
                        $defVal = 0;
                        break;
                    }
                    case 'float':{
                        $defVal = 0.0;
                        break;
                    }
                    case 'string':{
                        $defVal = '';
                        break;
                    }
                    case 'array':{
                        $defVal = [];
                        break;
                    }
                }
            }
            $jsonData[$propertyName] = $defVal;
        }
        return $jsonData;
    }
}